/*
    This file is part of ServerProxy.
    SocketProxy is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    Subsonic is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
    Copyright 2014 (C) Scott Jackson
*/

package net.nullsum.audinaut.util;

import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

public abstract class ServerProxy implements Runnable {
    private static final String TAG = ServerProxy.class.getSimpleName();
    boolean isRunning;
    private Thread thread;
    private ServerSocket socket;
    private int port;

    ServerProxy() {
        // Create listening socket
        try {
            socket = new ServerSocket(0);
            socket.setSoTimeout(5000);
            port = socket.getLocalPort();
        } catch (UnknownHostException e) { // impossible
        } catch (IOException e) {
            Log.e(TAG, "IOException initializing server", e);
        }
    }

    public void start() {
        if (socket.isBound()) {
            thread = new Thread(this, "Socket Proxy");
            thread.start();
        } else {
            Log.e(TAG, "Attempting to start a non-initialized proxy");
        }
    }

    public void stop() {
        isRunning = false;
        if (thread != null) {
            thread.interrupt();
        }
    }

    public String getPrivateAddress(String request) {
        return getAddress(request);
    }

    private String getAddress(String request) {
        try {
            return String.format("http://%s:%d/%s", "127.0.0.1", port, URLEncoder.encode(request, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    @Override
    public void run() {
        isRunning = true;
        while (isRunning) {
            try {
                Socket client = socket.accept();
                if (client == null) {
                    continue;
                }
                Log.i(TAG, "client connected");

                ProxyTask task = getTask(client);
                if (task.processRequest()) {
                    new Thread(task, "ProxyTask").start();
                }

            } catch (SocketTimeoutException e) {
                // Do nothing
            } catch (IOException e) {
                Log.e(TAG, "Error connecting to client", e);
            }
        }
        Log.i(TAG, "Proxy interrupted. Shutting down.");
    }

    abstract ProxyTask getTask(Socket client);

    protected abstract class ProxyTask implements Runnable {
        final Socket client;
        final Map<String, String> requestHeaders = new HashMap<>();
        String path;
        int cbSkip = 0;

        public ProxyTask(Socket client) {
            this.client = client;
        }

        boolean readRequest() {
            InputStream is;
            String firstLine;
            BufferedReader reader;
            try {
                is = client.getInputStream();
                reader = new BufferedReader(new InputStreamReader(is), 8192);
                firstLine = reader.readLine();
            } catch (IOException e) {
                Log.e(TAG, "Error parsing request", e);
                return false;
            }

            if (firstLine == null) {
                Log.i(TAG, "Proxy client closed connection without a request.");
                return false;
            }

            StringTokenizer st = new StringTokenizer(firstLine);
            if (!st.hasMoreTokens()) {
                Log.w(TAG, "Unknown request with no tokens");
                return false;
            } else if (st.countTokens() < 2) {
                Log.w(TAG, "Unknown request with no uri: \"" + firstLine + '"');
                return false;
            }
            String uri = st.nextToken();
            String realUri = uri.substring(1);

            // Process path
            try {
                path = URLDecoder.decode(realUri, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                Log.e(TAG, "Unsupported encoding", e);
                return false;
            }

            // Get all of the headers
            try {
                String line;
                while ((line = reader.readLine()) != null && !"".equals(line)) {
                    int index = line.indexOf(':');
                    // Ignore headers without ':' or where ':' is the last thing in the string
                    if (index != -1 && (index + 2) < line.length()) {
                        String headerName = line.substring(0, index);
                        String headerValue = line.substring(index + 2);

                        requestHeaders.put(headerName, headerValue);
                    }
                }
            } catch (IOException e) {
                // Don't really care once past first line
            } catch (Exception e) {
                Log.w(TAG, "Exception reading request", e);
            }

            return true;
        }

        public boolean processRequest() {
            if (!readRequest()) {
                return false;
            }
            Log.i(TAG, "Processing request for " + path);

            // Try to get range requested
            String range = requestHeaders.get("Range");
            if (range != null) {
                int index = range.indexOf("=");
                if (index >= 0) {
                    range = range.substring(index + 1);

                    index = range.indexOf("-");
                    if (index > 0) {
                        range = range.substring(0, index);
                    }

                    cbSkip = Integer.parseInt(range);
                }
            }

            return true;
        }
    }
}
